﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MessageLibrary;
using MessageLibrary.Messages;
using GamedataLibrary;
using GamedataLibrary.Characters;
using GamedataLibrary.Jutsus;

namespace GamedataLibrary
{
    /// <summary>
    /// Trida reprezentujici instanci boje.
    /// </summary>
    class Fight
    {
        #region Fields

        Fighter fighter1;
        Fighter fighter2;
        Server server;
        Random random;
        
        #endregion // Fields

        #region Properties

        public Fighter Fighter1 { get { return fighter1; } }

        public Fighter Fighter2 { get { return fighter2; } }

        #endregion // Properties

        /// <summary>
        /// Vytvori instanci boje.
        /// </summary>
        /// <param name="server">Reference na server.</param>
        public Fight(Server server)
        {
            this.server = server;
            this.random = new Random();

            InitializeFight();
        }

        /// <summary>
        /// Inicializuje instanci boje.
        /// </summary>
        private void InitializeFight()
        {
            // prijmu inicialni info od hracu
            server.Clients[0].Receive();
            server.Clients[1].Receive();

            // pockam, az budu mit info od obou hracu
            server.Clients[0].Join();
            server.Clients[1].Join();

            Console.WriteLine("Both players have selected their characters.");

            // zpracuju prvniho hrace
            int type;
            Message msg;
            MessageComposer.Deserialize(server.Clients[0].Buffer, out type, out msg);

            if (type != (int)MessageLibrary.MessageType.Action)
                throw new Exception("ERROR");
            else
            {
                ActionMessage actionMsg = (ActionMessage)msg;

                if (actionMsg.Action == (int)CharactersID.Naruto)
                    fighter1 = new Fighter(CharactersID.Naruto, new Naruto());
                else if (actionMsg.Action == (int)CharactersID.Sasuke)
                    fighter1 = new Fighter(CharactersID.Sasuke, new Sasuke());
                else if (actionMsg.Action == (int)CharactersID.Sakura)
                    fighter1 = new Fighter(CharactersID.Sakura, new Sakura());
                else if (actionMsg.Action == (int)CharactersID.Minato)
                    fighter1 = new Fighter(CharactersID.Minato, new Minato());
                else if (actionMsg.Action == (int)CharactersID.Hiruzen)
                    fighter1 = new Fighter(CharactersID.Hiruzen, new Hiruzen());
            }

            // zpracuju druheho hrace
            MessageLibrary.MessageComposer.Deserialize(server.Clients[1].Buffer, out type, out msg);

            if (type != (int)MessageLibrary.MessageType.Action)
                throw new Exception("ERROR");
            else
            {
                ActionMessage actionMsg = (ActionMessage)msg;
                
                if (actionMsg.Action == (int)CharactersID.Naruto)
                    fighter2 = new Fighter(CharactersID.Naruto, new Naruto());
                else if (actionMsg.Action == (int)CharactersID.Sasuke)
                    fighter2 = new Fighter(CharactersID.Sasuke, new Sasuke());
                else if (actionMsg.Action == (int)CharactersID.Sakura)
                    fighter2 = new Fighter(CharactersID.Sakura, new Sakura());
                else if (actionMsg.Action == (int)CharactersID.Minato)
                    fighter2 = new Fighter(CharactersID.Minato, new Minato());
                else if (actionMsg.Action == (int)CharactersID.Hiruzen)
                    fighter2 = new Fighter(CharactersID.Hiruzen, new Hiruzen());
            }

            Console.WriteLine("Fighters are created.");

            /// odesleme hracum, kdo je jejich protivnik
            // vytvori zpravu
            ActionMessage a = new ActionMessage();
            a.Action = (int)fighter2.ID;

            // prevede zpravu do binarni podoby
            byte[] buffer = MessageComposer.Serialize((int)MessageType.Action, a);
            // odesle buffer hraci 1
            server.Clients[0].Send(buffer);

            // odesleme ïnfo hraci 2
            a.Action = (int)fighter1.ID;
            buffer = MessageComposer.Serialize((int)MessageType.Action, a);
            server.Clients[1].Send(buffer);

            Console.WriteLine("Sent info to both players.");
        }

        /// <summary>
        /// Odstartuje a ridi prubeh boje.
        /// </summary>
        public void Start()
        {
            // dokud jsou oba hraci zivy, vedeme boj v tazich
            while (fighter1.IsAlive() && fighter2.IsAlive())
            {
                fighter1.RecentlyStunned = false;
                fighter2.RecentlyStunned = false;

                // prijmu akci od prvniho hrace
                server.Clients[0].Receive();
                // prijmu akci od druheho hrace
                server.Clients[1].Receive();

                // pockam, az budu mit akci od obou hracu
                server.Clients[0].Join();
                server.Clients[1].Join();

                // vyprazdnim logy obou hracu
                fighter1.Log = "";
                fighter2.Log = "";

                // kazde obranne jutsu se ma provest pred jakymkoliv utokem
                ActionMessage a = (ActionMessage)server.Clients[0].Message;
                if (a.Action == 3)
                {
                    Defend((DefenseJutsu)fighter1.Character.Jutsu4, fighter1, fighter2);
                    fighter1.Jutsu4Cooldown = fighter1.Character.Jutsu4.Cooldown; 
                }

                a = (ActionMessage)server.Clients[1].Message;
                if (a.Action == 3)
                {
                    Defend((DefenseJutsu)fighter2.Character.Jutsu4, fighter2, fighter1);
                    fighter2.Jutsu4Cooldown = fighter2.Character.Jutsu4.Cooldown;
                }

                // provedu zvolenou akci
                DoAction(0);
                DoAction(1);

                // po tahu vynulujeme obrany hracu.
                fighter1.Defense = 0;
                fighter2.Defense = 0;

                /// odeslu zpatky hracum stav boje
                // odeslu prvnimu hraci
                int type = (int)MessageType.State;
                StateMessage msg = CreateStateMessage(0);
                byte[] buffer = MessageComposer.Serialize(type, msg);
                server.Clients[0].Send(buffer);
                // odeslu druhemu hraci
                msg = CreateStateMessage(1);
                buffer = MessageComposer.Serialize(type, msg);
                server.Clients[1].Send(buffer);
            }
        }

        /// <summary>
        /// Provede hracem zvolenou akci
        /// </summary>
        /// <param name="i">index klienta</param>
        private void DoAction(int i)
        {
            // deklarace lokalnich promennych
            ServerCommunication myClient;
            ServerCommunication enemyClient;
            Fighter me;
            Fighter enemy;

            if (i == 0)
            {
                myClient = server.Clients[0];
                enemyClient = server.Clients[1];
                me = fighter1;
                enemy = fighter2;
            }
            else
            {
                myClient = server.Clients[1];
                enemyClient = server.Clients[0];
                me = fighter2;
                enemy = fighter1;
            }

            ActionMessage a = (ActionMessage)myClient.Message;

            DecreaseCooldowns(me);
            
            // pokud jsem zastunovany, ale ne tenhle tah
            if (me.Stunned > 0 && !me.RecentlyStunned)
            {
                DecreaseStunDurations(me);
                me.Log += "My " + me.Character.Name + " is stunned for " + me.Stunned + " rounds.\n";
                enemy.Log += "Enemy " + me.Character.Name + "is stunned for " + me.Stunned + " rounds.\n";
            }
            else
            {
                if (a.Action == 0)
                {
                    Attack((DamageJutsu)me.Character.Jutsu1, me, enemy);
                    me.Jutsu1Cooldown = me.Character.Jutsu1.Cooldown;
                }
                else if (a.Action == 1)
                {
                    Attack((DamageJutsu)me.Character.Jutsu2, me, enemy);
                    me.Jutsu2Cooldown = me.Character.Jutsu2.Cooldown;
                }
                else if (a.Action == 2)
                {
                    Stun((StunJustu)me.Character.Jutsu3, me, enemy);
                    me.Jutsu3Cooldown = me.Character.Jutsu3.Cooldown;
                }
            }

        }

        private void DecreaseCooldowns(Fighter fighter)
        {
            if (fighter.Jutsu1Cooldown > 0) fighter.Jutsu1Cooldown--;
            if (fighter.Jutsu2Cooldown > 0) fighter.Jutsu2Cooldown--;
            if (fighter.Jutsu3Cooldown > 0) fighter.Jutsu3Cooldown--;
            if (fighter.Jutsu4Cooldown > 0) fighter.Jutsu4Cooldown--;
        }

        private void DecreaseStunDurations(Fighter fighter)
        {
            if (fighter.Stunned > 0) fighter.Stunned--;
        }

        /// <summary>
        /// Provede utok danou technikou na protivnika.
        /// </summary>
        /// <param name="jutsu">Bojova technika.</param>
        /// <param name="enemy">Cil utoku.</param>
        private void Attack(DamageJutsu jutsu, Fighter me, Fighter enemy)
        {
            int chance = 0;

            if (enemy.Stunned == 0)
            {
                // sance na uspech z techniky
                chance = jutsu.Chance;
                // odecteme obranu protivnika
                chance -= enemy.Defense;
                // po tom, co se protivnik branil, mu vynulujeme obranu (obrana je jen na 1 kolo)
                enemy.Defense = 0;
            }
            else
            {
                chance = 100;
            }

            // pokud jsme se trefili, tak provedeme utok
            if (Hit(chance))
            {
                // spocteme dmg
                int dmg = jutsu.Damage;
                
                // odecteme damage z protivnikova zivota
                enemy.Health -= dmg;
                
                // zalogujeme posledni utok
                me.Log += "My " + me.Character.Name + " attacked at enemy " + enemy.Character.Name + 
                    " with " + jutsu.Name + " and dealt " + dmg + " damage.\n";
                enemy.Log += "Enemy " + me.Character.Name + " attacked at my " + enemy.Character.Name + 
                    " with " + jutsu.Name + " and dealt " + dmg + " damage.\n";
            }
            else // jinak se netrefil
            {
                me.Log += "My " + me.Character.Name + " attacked at enemy " + enemy.Character.Name + 
                    " with " + jutsu.Name + " but missed.\n";
                enemy.Log += "Enemy " + me.Character.Name + " attacked at my " + enemy.Character.Name + 
                    " with " + jutsu.Name + " but missed.\n";
            }
        }

        private void Stun(StunJustu jutsu, Fighter me, Fighter enemy)
        {
            int chance = 0;

            if (enemy.Stunned == 0)
            {
                // sance na uspech z techniky
                chance = jutsu.Chance;
                // odecteme obranu protivnika
                chance -= enemy.Defense;
                // po tom, co se protivnik branil, mu vynulujeme obranu (obrana je jen na 1 kolo)
                enemy.Defense = 0;
            }
            else
            {
                chance = 100;
            }

            if (Hit(chance))
            {
                enemy.Stunned = jutsu.Duration;
                enemy.RecentlyStunned = true;

                // zalogujeme posledni utok
                me.Log += "My " + me.Character.Name + " stunned enemy " + enemy.Character.Name +
                    " with " + jutsu.Name + ".\n";
                enemy.Log += "Enemy " + me.Character.Name + " stunned my " + enemy.Character.Name +
                    " with " + jutsu.Name + ".\n";
            }
            else 
            {
                me.Log += "My " + me.Character.Name + " tried to stun enemy " + enemy.Character.Name +
                    " with " + jutsu.Name + " but missed.\n";
                enemy.Log += "Enemy " + me.Character.Name + " tried to stun my " + enemy.Character.Name +
                    " with " + jutsu.Name + " but missed.\n";
            }
        }

        private void Defend(DefenseJutsu jutsu, Fighter me, Fighter enemy)
        {
            me.Defense = jutsu.Defense;

            me.Log += "My " + me.Character.Name + " used " + jutsu.Name + 
                " to increase defense by " + jutsu.Defense+ "%.\n";
            enemy.Log += "Enemy " + me.Character.Name + " used " + jutsu.Name +
                " to increase defense by " + jutsu.Defense + "%.\n";
        }

        /// <summary>
        /// Generator sanci, jestli se technika povede nebo ne.
        /// </summary>
        /// <param name="chance">Sance.</param>
        /// <returns>Vrati TRUE, pokud se povedlo.</returns>
        private bool Hit(int chance)
        {
            int i = random.Next(0, 100);

            Console.WriteLine(i.ToString());

            if (i <= chance) return true;
            else return false;
        }

        /// <summary>
        /// Vytvori zpravu o stavu boje pro jednotliveho hrace.
        /// </summary>
        /// <param name="i">Index hrace (0 nebo 1).</param>
        /// <returns></returns>
        private StateMessage CreateStateMessage(int i)
        {
            // deklarace lokalnich promennych
            Fighter me; 
            Fighter enemy;
            
            if (i == 0)
            {
                me = fighter1;
                enemy = fighter2;
            }
            else
            {
                me = fighter2;
                enemy = fighter1;
            }
            
            StateMessage msg = new StateMessage();

            msg.MyHealth = me.Health;
            msg.EnemyHealth = enemy.Health;

            msg.MeStunned = me.Stunned;
            msg.EnemyStunned = enemy.Stunned;

            msg.Jutsu1Cooldown = me.Jutsu1Cooldown;
            msg.Jutsu2Cooldown = me.Jutsu2Cooldown;
            msg.Jutsu3Cooldown = me.Jutsu3Cooldown;
            msg.Jutsu4Cooldown = me.Jutsu4Cooldown;
            
            msg.MyLog = me.Log;

            return msg;
        }
    }
}
